<!doctype html>
<html>
  <head>
    <title>Notifications: Property reflection in the "notificationclick" event and SWR.getNotifications().</title>
    <script src="../resources/testharness.js"></script>
    <script src="../resources/testharnessreport.js"></script>
    <script src="../serviceworker/resources/test-helpers.js"></script>
    <script src="resources/test-helpers.js"></script>
  </head>
  <body>
    <script>
      // Tests that the notification object in a) the "notificationclick" event in the
      // Service Worker, and b) ServiceWorkerRegistration.getNotifications(), both
      // accurately reflect the attributes with which the notification was created.

      // Serializes a trigger object to send via postMessage.
      // If the given object is not a trigger, return the same object.
      function serializeTrigger(trigger) {
          if (trigger instanceof TimestampTrigger)
              return { timestamp: trigger.timestamp };
          return trigger;
      }

      // Serialize options to send via sendMessage and for deep comparison.
      function serializeOptions(options) {
          return {
              ...options,
              showTrigger: serializeTrigger(options.showTrigger),
          };
      }

      // Checks that all the properties in expected also exist and are equal in actual.
      function assert_object_is_superset(actual, expected, description) {
          Object.keys(expected).forEach(function(key) {
              // Serialize fields that are triggers so we can compare them.
              var fieldActual = serializeTrigger(actual[key]);
              var fieldExpected = serializeTrigger(expected[key]);
              var fieldDescription = description + ' [field ' + key + ']';
              if (typeof fieldExpected == 'object')
                  assert_object_equals(fieldActual, fieldExpected, fieldDescription);
              else
                  assert_equals(fieldActual, fieldExpected, fieldDescription);
          });
      }

      async_test(function(test) {
          var scope = 'resources/scope/' + location.pathname;
          var script = 'instrumentation-service-worker.js';
          var port;
          var registration;

          var options = {
              title: scope,
              dir: 'rtl',
              lang: 'nl-NL',
              body: 'Hello, world!',
              tag: 'tag',
              // FIXME: Relative URLs for the icon attribute currently get reflected as
              // an absolute URL, which should probably be the given relative URL.
              image: 'https://example/image.jpg',
              icon: 'https://example/icon.png',
              badge: 'https://example/badge.png',
              vibrate: [100, 200, 300],
              timestamp: 621046800000,
              renotify: true,
              silent: false,
              requireInteraction: true,
              data: [
                  { property: 'foobar',
                    string: '\uDFFF\u0000\uDBFF',
                    scalar: true },
                  12.15
              ],
              actions: [],
              // Make sure the notification is immediately triggered by setting the timestamp in the past.
              showTrigger: new TimestampTrigger(Date.now().valueOf() - 1000),
          };
          // Deliberately add more actions than are supported.
          for (var i = 0; i < 2 * Notification.maxActions; i++) {
              options.actions.push({
                  type: i % 2 == 0 ? 'button' : 'text',
                  action: 'a' + i,
                  title: 'Action ' + i,
                  icon: 'https://example/action_icon_' + i + '.png',
                  placeholder: i % 2 == 0 ? null : 'Type a reply...'
              });
          }

          if (window.testRunner) {
              testRunner.setPermission('notifications', 'granted', location.origin, location.origin);
          }
          getActiveServiceWorkerWithMessagePort(test, script, scope).then(function(info) {
              port = info.port;
              registration = info.registration;
              // (1) Tell the Service Worker to display a Web Notification.
              var showPromise = sendCommand(port, {
                  command: 'show',

                  title: scope,
                  options: serializeOptions(options)
              });

              // Now limit actions to the number that we expect to be reflected on notifications.
              options.actions = options.actions.slice(0, Notification.maxActions);
              return showPromise;
          }).then(function(data) {
              // (2) Confirm that the service worker displayed the notification successfully.
              assert_true(data.success, 'The notification must have been displayed.');
              return simulateNotificationClick(scope, -1 /* action_index */, port);
          }).then(function(data) {
              // (3) Confirm that all properties set on the cloned Notification object are as expected.
              assert_object_is_superset(data.notification, options, 'The Notification object properties must be the same in notificationclick events.');
              return registration.getNotifications({includeTriggered: true});
          }).then(function(notifications) {
              // (4) Check that the properties are also set correctly on the non-cloned Notification
              // object from getNotifications.
              assert_equals(notifications.length, 1);
              assert_object_is_superset(notifications[0], options, 'The Notification object properties must be the same in getNotifications.');

              notifications[0].actions.foo = 'bar';
              assert_throws_js(TypeError, () => notifications[0].actions.push({ title: 'Foo' }));

              if (notifications[0].actions.length) {
                  notifications[0].actions[0].title = 'Changed';
                  notifications[0].actions[0].foo = 'bar';
              }

              assert_object_equals(notifications[0].actions, options.actions, 'The actions field should be immutable.');

              assert_equals(notifications[0].actions, notifications[0].actions, '`actions` attribute equality');
              assert_equals(notifications[0].data, notifications[0].data, '`data` attribute equality');
              assert_equals(notifications[0].vibrate, notifications[0].vibrate, '`vibrate` attribute equality');
              assert_equals(notifications[0].showTrigger, notifications[0].showTrigger, '`showTrigger` attribute equality');
              test.done();
          }).catch(unreached_rejection(test));

      }, 'Clicking on a notification displayed by a Service Worker the notificationclick event.');
    </script>
  </body>
</html>
